﻿////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// <copyright>Copyright 2008-2011 Andreas Huber Doenni</copyright>
// Distributed under the GNU General Public License version 2 (GPLv2).
// See accompanying file License.txt or copy at http://phuse.codeplex.com/license.
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

namespace Phuse.Net.Forums
{
    using System;
    using System.Collections.Generic;
    using System.Diagnostics.CodeAnalysis;
    using System.IO;
    using System.IO.IsolatedStorage;
    using System.Linq;

    /// <summary>Provides a consistent interface for the file I/O provided by the different platforms.</summary>
    internal sealed class FileIOProvider : IDisposable
    {
        private readonly string rootDirectory;
#if WINDOWS_PHONE
        private readonly IsolatedStorageFile isolated = IsolatedStorageFile.GetUserStoreForApplication();
#endif

        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////

        /// <summary>See <see cref="IDisposable.Dispose"/>.</summary>
        public void Dispose()
        {
#if WINDOWS_PHONE
            this.isolated.Dispose();
#endif
        }

        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////

        internal FileIOProvider(string rootDirectory)
        {
            // Make sure rootDirectory does not end in Path.DirectorySeparatorChar
            this.rootDirectory = Path.GetDirectoryName(rootDirectory + Path.DirectorySeparatorChar);
        }

        internal string RootDirectory
        {
            get { return this.rootDirectory; }
        }

        internal void CreateDirectory(string relativeDirectory)
        {
            var directory = Path.Combine(this.rootDirectory, relativeDirectory);
#if WINDOWS_PHONE
            try
            {
                this.isolated.CreateDirectory(directory);
            }
            catch (IsolatedStorageException ex)
            {
                if (ex.Message.StartsWith("An error occurred while ", StringComparison.Ordinal))
                {
                    throw new ArgumentException("Unable to create directory: " + directory, ex);
                }
            }
#else
            Directory.CreateDirectory(directory);
#endif
        }

        internal void DeleteDirectory(string relativeDirectory)
        {
            var directory = Path.Combine(this.rootDirectory, relativeDirectory);
#if WINDOWS_PHONE
            this.isolated.DeleteDirectory(directory);
#else
            Directory.Delete(directory);
#endif
        }

        internal bool IsEmptyDirectory(string relativeDirectory)
        {
            var directory = Path.Combine(this.rootDirectory, relativeDirectory);
#if WINDOWS_PHONE
            var pattern = Path.Combine(directory, "*");
            return (this.isolated.GetFileNames(pattern).Length == 0) &&
                (this.isolated.GetDirectoryNames(pattern).Length == 0);
#else
            return (Directory.GetDirectories(directory).Length == 0) && (Directory.GetFiles(directory).Length == 0);
#endif
        }

        internal string[] GetDirectories(string relativeDirectory)
        {
            var directory = Path.Combine(this.rootDirectory, relativeDirectory);
#if WINDOWS_PHONE
            return PrependDirectory(relativeDirectory, this.isolated.GetDirectoryNames(Path.Combine(directory, "*")));
#else
            return this.RemoveRootDirectory(Directory.EnumerateDirectories(directory));
#endif
        }

#if WINDOWS_PHONE
        [SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "bufferSize", Justification = "There's no way to specify a buffer size.")]
#endif
        internal FileStream OpenFile(string relativePath, FileMode mode, FileAccess access, FileShare share, int bufferSize = 0x1000)
        {
            var path = Path.Combine(this.rootDirectory, relativePath);
#if WINDOWS_PHONE
            try
            {
                // bufferSize parameter is missing on the phone
                return this.isolated.OpenFile(path, mode, access, share);
            }
            catch (IsolatedStorageException ex)
            {
                if (ex.Message.StartsWith("Operation not permitted", StringComparison.Ordinal))
                {
                    throw new IOException("Unable to open file: " + path, ex);
                }

                throw;
            }
#else
            return new FileStream(path, mode, access, share, bufferSize);
#endif
        }

        internal void DeleteFile(string relativePath)
        {
            var path = Path.Combine(this.rootDirectory, relativePath);
#if WINDOWS_PHONE
            this.isolated.DeleteFile(path);
#else
            File.Delete(path);
#endif
        }

        internal void MoveFile(string relativeSourcePath, string relativeDestinationPath)
        {
            var sourcePath = Path.Combine(this.rootDirectory, relativeSourcePath);
            var destinationPath = Path.Combine(this.rootDirectory, relativeDestinationPath);
#if WINDOWS_PHONE
            this.isolated.MoveFile(sourcePath, destinationPath);
#else
            File.Move(sourcePath, destinationPath);
#endif
        }

        internal string[] GetFiles(string relativeDirectory, string searchPattern)
        {
            var directory = Path.Combine(this.rootDirectory, relativeDirectory);
#if WINDOWS_PHONE
            return PrependDirectory(
                relativeDirectory, this.isolated.GetFileNames(Path.Combine(directory, searchPattern)));
#else
            return this.RemoveRootDirectory(Directory.EnumerateFiles(directory, searchPattern));
#endif
        }

        internal void Delete(string relativePath)
        {
            var path = Path.Combine(this.rootDirectory, relativePath);
#if WINDOWS_PHONE
            if (this.isolated.FileExists(path))
            {
                this.isolated.DeleteFile(path);
            }
            else if (this.isolated.DirectoryExists(path))
            {
                this.DeleteNonEmptyDirectory(path);
            }
#else
            if (File.Exists(path))
            {
                File.Delete(path);
            }
            else if (Directory.Exists(path))
            {
                Directory.Delete(path, true);
            }
#endif
        }

        internal string GetDatabaseConnectionString(string relativeDatabaseFilename)
        {
            var connectionString = Path.Combine(this.rootDirectory, relativeDatabaseFilename);
#if WINDOWS_PHONE
            connectionString = Path.Combine("isostore:\\", connectionString);
#endif
            return connectionString;
        }

        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////

#if WINDOWS_PHONE
        private void DeleteNonEmptyDirectory(string directory)
        {
            var searchPattern = Path.Combine(directory, "*");

            foreach (var subDirectory in this.isolated.GetDirectoryNames(searchPattern))
            {
                this.DeleteNonEmptyDirectory(Path.Combine(directory, subDirectory));
            }

            foreach (var file in this.isolated.GetFileNames(searchPattern))
            {
                this.isolated.DeleteFile(Path.Combine(directory, file));
            }

            this.isolated.DeleteDirectory(directory);
        }

        private static string[] PrependDirectory(string relativeDirectory, IEnumerable<string> fileNames)
        {
            return fileNames.Select(name => Path.Combine(relativeDirectory, name)).ToArray();
        }
#else
        private string[] RemoveRootDirectory(IEnumerable<string> paths)
        {
            return paths.Select(dir => dir.Remove(0, this.rootDirectory.Length + 1)).ToArray();
        }
#endif
    }
}
